﻿/**
 * author:  Martin Hrabos
 * year:    2016
 * email:   hrabosmartin@gmail.com
 * 
 * FIT Brno University of Technology / ASCR Brno
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;

namespace CorrCL
{
    /*
     * Class related to computations on CPU
     */
    class CpuComputation : Computation
    {
        // Array for storing computed coefficients
        protected float[] result;

        // Run computation on CPU
        public override void compute()
        {
            long workItems = (2 * offset) + 1;
            result = new float[workItems];

            int progress = 0;

            // Get number of computations
            int computationCount = ((data.Count * data.Count) - data.Count) / 2;
            int processedComputations = 0;

            OnProgressChanged(0, "Executing");
            Stopwatch sw = new Stopwatch();
            sw.Start();


            for (int i = 0; i < data.Count - 1; i++)
            {
                for (int j = i + 1; j < data.Count; j++)
                {

                    long startIdx = 0;
                    long endIdx = 0;
                    List<float[]> blockList = new List<float[]>();

                    do
                    {
                        endIdx = startIdx + windowSize - 1;
                        if (endIdx > size)
                            endIdx = size;
                        long currentWindowSize = endIdx - startIdx;
                        float[] xData = new float[currentWindowSize];
                        float[] yData = new float[currentWindowSize];
                        Array.Copy(data[i].ToArray(), startIdx, xData, 0, currentWindowSize);
                        Array.Copy(data[j].ToArray(), startIdx, yData, 0, currentWindowSize);

                        float[] item = computeBlockCPU(startIdx, endIdx, xData, yData, offset);
                        blockList.Add((float[])item.Clone());

                        startIdx += windowOffset;
                        Debug.WriteLine(startIdx);
                    }

                    while (endIdx < size - 1);

                    // Add result to ResultMatrix
                    rm.Add(new List<float[]>(blockList), j - 1, i);

                    processedComputations++;
                    progress = (int)((processedComputations / (float)computationCount) * 100);
                    OnProgressChanged(progress, String.Format("Processed {0} %", progress));
                }
            }

            sw.Stop();

            TimeSpan t = sw.Elapsed;
            string answer = string.Format("{0:D2}:{1:D2}.{2:D3}",
                                    t.Minutes,
                                    t.Seconds,
                                    t.Milliseconds);


            OnProgressChanged(100, "Total Time: " + answer);
        }

        private float[] computeBlockCPU (long startIdx, long endIdx, float[] xData, float[] yData, long offset)
        {
            float[] result = new float[2 * offset + 1];
            long windowSize = endIdx - startIdx;

            ChildData cData = new ChildData(xData, yData, windowSize, offset, THREADS_COUNT);

            result = cData.result;

            return result;
        }
    }
    /*
     * Class is running computations on CPU in separated threads
     */
    internal class ChildData
    {

        protected float[] x;
        protected float[] y;
        protected long size;
        protected long offset;
        public float[] result;

        public ChildData() { }
        public ChildData(float[] val1, float[] val2, long s, long o, int threadsCount)
        {
            x = val1;
            y = val2;
            size = s;
            offset = o;
            result = new float[2 * offset + 1];
            // Create list of threads
            IList<Thread> threads = new List<Thread>();
            long workItems = 2 * offset + 1;

            for (int k = 0; k < workItems; k += threadsCount)
            {

                for (int l = 0; (k + l < workItems) && (l < threadsCount); l++)
                {
                    // Create threads
                    Thread t = new Thread(launch);
                    t.IsBackground = true;
                    t.Name = (k + l).ToString();
                    threads.Add(t);
                    t.Start();
                }
            }

            foreach (var thread in threads)
            {
                // Wait for threads to finish
                thread.Join();
            }
        }

        // Run function for thread
        public void launch()
        {
            // Get thread idx
            int idx = Int32.Parse(Thread.CurrentThread.Name);
            long localIdx = 0;
            float res;
            long resultIdx = 0;
            

            if (idx == 0)
            {
                resultIdx = offset;
                localIdx = 0;
            }
            else if (idx <= offset)
            {
                resultIdx = (idx - offset) * (-1);
                localIdx = idx;
            }
            else if (idx > offset)
            {
                resultIdx = idx;
                localIdx = idx - offset;
            }


            if (resultIdx > 2 * offset + 1)
                return;

            float meanX;
            float meanY;
            float sumX;
            float sumY;
            float sumXX;
            float sumYY;
            float sumXY;
            meanX = 0.0f;
            meanY = 0.0f;
            sumX = 0.0f;
            sumY = 0.0f;
            sumXX = 0.0f;
            sumYY = 0.0f;
            sumXY = 0.0f;

            if (idx <= offset)
            {
                int i = 0;
                float actX = 0.0f;
                float actY = 0.0f;
                for (i = 0; i < size - localIdx; i++)
                {
                    actX = x[i];
                    actY = y[i + localIdx];
                    sumX += actX;
                    sumXX += actX * actX;
                    sumY += actY;
                    sumYY += actY * actY;
                    sumXY += actX * actY;

                }


                meanX = sumX / i;
                meanY = sumY / i;

                res = ((sumXY / i) - (meanX * meanY)) / (((float)Math.Sqrt((sumXX / i) - (meanX * meanX))) * ((float)Math.Sqrt((sumYY / i) - (meanY * meanY))));
                if (float.IsNaN(res))
                    res = 0.0f;
                result[resultIdx] = res;
                

            }
            else if (idx > offset)
            {
                int i = 0;
                float actX = 0.0f;
                float actY = 0.0f;
                for (i = 0; i < size - localIdx; i++)
                {
                    actX = x[i + localIdx];
                    actY = y[i];
                    sumX += actX;
                    sumXX += actX * actX;
                    sumY += actY;
                    sumYY += actY * actY;
                    sumXY += actX * actY;
                }

                meanX = sumX / i;
                meanY = sumY / i;
                res = ((sumXY / i) - (meanX * meanY)) / (((float)Math.Sqrt((sumXX / i) - (meanX * meanX))) * ((float)Math.Sqrt((sumYY / i) - (meanY * meanY))));
                if (float.IsNaN(res))
                    res = 0.0f;
                result[resultIdx] = res;
            }
        }

    }
}
